# -*- coding: utf-8 -*-
import paho.mqtt.client as mqtt
import os
import time
import re
import subprocess
import shutil 
import hashlib
import threading
import smtplib
import json
import  copy
import logging
from  handleEmail import  HandleEmail
import lua_script
#publish_8910v1.3_at_liangjian@airm2m.com
#publish_1802S_at
#publish_8910v1.3_lua_bt_float
class  mqttRun():
    def __init__(self):
        self.IDLE = 0
        self.START = 1
        self.COMPLATE = 2
        self.RUN = 3
        self.scriptSize = 720860
        self.state = self.IDLE
        self.email = ""
        self.IsComplat = False
        self.AIRSOURCE = "D:\\AirSource\\"
        self.MYPLACE = "D:\\automatic-publishing-system\\server"
        self.circulateTimeOut = 60
        self.start_circulateTimeOut = 130
        self.circulate_test_req_timer = None
        self.ErrNumber = 5
        self.testTesultEndReq_timer = None
        self.logger = logging.getLogger('mainServer')
        self.logger.setLevel(level=logging.INFO)
        LOG_FORMAT = '%(asctime)s.%(msecs)03d line:%(lineno)d %(levelname)s %(message)s'
        DATE_FORMAT = '%Y-%m-%d %X'
        console = logging.StreamHandler()
        formatter = logging.Formatter(fmt=LOG_FORMAT, datefmt=DATE_FORMAT)
        console.setFormatter(formatter)
        console.setLevel(logging.INFO)
        self.luaPath = "D:\AirSource\8910v1.3\\target\8915DM_cat1_LUA_Full_Function\lua.img"
        self.luaPath2 = "D:\AirSource\8910v1.2\\target\8915DM_cat1_LUA_Full_Function\lua.img"
        self.logger.addHandler(console)
        self.check_script = lua_script.CheckScript(self.logger)
        self.HandleEmail = HandleEmail(self.logger)
        #参数1,当前状态 参数2,测试总时长 参数3,开始测试时间 参数4 循环查询定时器handle  参数5 总测试时常定时器
        testMessageDict = {
                "state":self.IDLE,
                "totalTime":0,
                "startTime":0,
                "circulateHandle":0,
                "circulateFlag":0,
                "endHandle":0,
                "email":0,
                "ver":False,
                "Errlog":"",
                "state":0,
                "svnRevison":0
                 }

        self.TestProcess = {
            "at":copy.deepcopy(testMessageDict),
            "lua":copy.deepcopy(testMessageDict),
            "csdk":copy.deepcopy(testMessageDict)
            }

        self.testTimeTotal = 0
        self.testStartTime = 0


        
    def start_server(self):
        os.system('mosquitto  -c mosquitto.conf')
        

    def compile(self,build ,times =10 ,product = ""):
        for i in range(0,times):
            res = self.execute_command(build,product = product)
            # res = os.system('build')
            self.logger.info(res)
            if res :
                return True
          
        return False
                        
    def getmd5(self,file):
        m = hashlib.md5()
        with open(file,'rb') as f:
            for line in f:
                m.update(line)
        md5code = m.hexdigest()
        self.logger.info(md5code)
        return md5code

    def update_and_compile(self,product,project):
        self.logger.info("start update svn")
        self.IsComplat = True
        compiletSussFlag = False
        res , needCompile = self.update_svn(product,project)
        if res:
            os.chdir(self.AIRSOURCE + product)
            if product.find("8910") != -1:
                self.logger.info("start remove out")
                if os.path.exists(self.AIRSOURCE + product + "\hex"):
                    for root, dirs , files in os.walk(self.AIRSOURCE + product + "\hex"):
                        for file in dirs:
                            full_path = os.path.join(root, file)
                            if file.lower().find(project) != -1:
                                shutil.rmtree(full_path)
                # if needCompile:
                #     os.system('clean')          
                #     pass   
                if project == "at":
                    self.logger.info("start complets")
                    if self.compile("build",product = product):
                        self.logger.info("update_and_compile succ !!!")
                        compiletSussFlag = True
                    else:
                         self.logger.error("update_and_compile false !!!")                        
                elif project == "lua"  :
                    if self.compile("build_lua_Full_Function.bat release",product = product):
                        self.logger.info("update_and_compile succ !!!")
                        compiletSussFlag = True
                    else:
                         self.logger.error("update_and_compile false !!!")
        else:
            self.logger.error("update false !!!")

        self.IsComplat = False    
        time.sleep(2)
        if compiletSussFlag :
            filePath = self.GetPacFile(project)
            os.chdir(self.MYPLACE)
            if filePath :
                path= self.AIRSOURCE + product + "//" + filePath
                return path
            else:
                return False
        else:
            os.chdir(self.MYPLACE)
            self.on_publish("airm2m/server_info%s"%project,"编译失败，请检查代码".encode("gbk"),1)
            self.state  = self.IDLE
            return False

    def  splite_mes(self,mes):
        dictS = json.loads(mes)
        return dictS["product"],dictS["project"],dictS["email"],dictS["testTime"]

    def time_timeout(self,project):
        if self.TestProcess[project]["state"] == self.RUN :
            self.on_publish("airm2m/server_endreq%s"%(project),1,1)
        else:
            self.HandleEmail.SendTestEmail(self.TestProcess[project]["email"],"测试异常!!!\r\n%s\r\n"%project)




    def circulate_test_req(self,project,timeout):
        if self.TestProcess[project]["circulateFlag"] == 1:
            self.endTest(project)
            self.HandleEmail.SendTestEmail(self.TestProcess[project]["email"],"测试电脑异常!!!\r\n%s\r\n"%project)
            return
        self.on_publish("airm2m/server_circulatereq%s"%(project),1,1)
        self.TestProcess[project]["circulateHandle"] = threading.Timer(timeout,self.circulate_test_req,args=(project,timeout))
        self.TestProcess[project]["circulateHandle"].start()
        self.TestProcess[project]["circulateFlag"] = 1

    def updateAndComplatThread(self,product,project,timeout):
        if project  == "lua":
            self.on_publish("airm2m/server_info%s"%project,"正在拉取和合并脚本".encode("gbk"),1)
            if product == "8910v1.3":
                self.combinScrpt(self.luaPath)
            else:
                self.combinScrpt(self.luaPath2)

        res = self.update_and_compile(product,project)
        if res :
            MMM = re.match(".*\\\(.*).pac",res)
            if MMM :
                hd = open(res,"rb")
                data = hd.read()
                hd.close()
                self.TestProcess[project]["ver"] = MMM.group(1)
                self.on_publish("airm2m/server_upgradeAndTestVer%s"%project,self.TestProcess[project]["ver"],1)
                time.sleep(2)
                self.on_publish("airm2m/server_upgradeAndTestMD5%s"%project,self.getmd5(res),1)
                
                self.on_publish("airm2m/server_upgradeAndTeststart%s"%project,data,1)
                self.TestProcess[project]["state"] = self.COMPLATE
                timeout = timeout*60
                self.TestProcess[project]["endHandle"] = threading.Timer(int(timeout),self.time_timeout,args=(project,))
                self.TestProcess[project]["endHandle"].start()
                
                self.TestProcess[project]["circulateHandle"] = threading.Timer(self.start_circulateTimeOut,self.circulate_test_req,args=(project,self.circulateTimeOut,))
                self.TestProcess[project]["circulateHandle"].start()
                self.TestProcess[project]["totalTime"] = int(timeout)
                self.TestProcess[project]["startTime"] = time.perf_counter()

                self.on_publish("airm2m/server_info%s"%project,"编译完成，已经发布给测试电脑".encode("gbk"),1)
                self.TestProcess[project]["state"] = self.RUN
            else:
                self.on_publish("airm2m/server_info%s"%project,"未获取到版本,请查看".encode("gbk"),1)
        else:
            self.logger.error("publish err")
            self.TestProcess[project]["state"] = self.IDLE
            self.HandleEmail.SendTestEmail(self.TestProcess[project]["email"],"SVN 更新或 编译异常!!!")

    def endTest(self,project):
        self.TestProcess[project]["circulateHandle"].cancel()
        self.TestProcess[project]["endHandle"].cancel()
        self.TestProcess[project]["circulateFlag"] = 0
        self.TestProcess[project]["state"] = self.IDLE
        pass    

    def analysis_recv_log(self,paylaod,project):
        self.logger.info("analysis_recv_log %d"%len(paylaod))
        try :
            jDict  = json.loads(paylaod)
            testimei = jDict["imei"]
            state = jDict["status"]
            htmlData = jDict["htmlData"]
            if testimei :
                self.logger.info("path:%s status:%s imei:%s"%(str(testimei),str(state),testimei))
                if project == "at":
                    if htmlData.count(": E") > self.ErrNumber:
                        return False,testimei,str(state),str(htmlData)
                        # self.SendTestEmail(self.TestProcess[project]["email"],"TEST FAIL!!!\r\n%s\r\n%s"%(tmepMatch.group(1),tmepMatch.group(3)))
                elif project == "lua":
                    if htmlData.count("[E]-") > self.ErrNumber :
                        return False,testimei,str(state),str(htmlData)
                
                return True,testimei,str(state),str(htmlData)
        except Exception as e:
            pass

        return False,False,False,False
                    

    def on_message_come(self,lient, userdata, msg):
        self.logger.info("topic:%s len:%d"%(msg.topic , len(msg.payload)))
        try:
            payload =  str(msg.payload, encoding = 'gbk')
        except Exception as  e:
            self.logger.warning("encoding gbk err")
            payload = str(msg.payload)
        if msg.topic == "airm2m/pc_publish":                                     #测试请求
            product,project,email,timeout  =  self.splite_mes(payload)
            self.logger.info("%s %s %s %s %s"%(product,project,email,timeout,self.TestProcess[project]["state"]))
            if product == "" or project == "" or email == "" or timeout == "":
                return
            
            if self.IsComplat :
                self.on_publish("airm2m/server_info%s"%project,"有其他任务正在编译，请稍后".encode("gbk"),1)
                return

            if  self.TestProcess[project]["state"]  ==  self.IDLE :
                self.TestProcess[project]["state"] = self.START
                self.TestProcess[project]["email"] = email
                self.on_publish("airm2m/server_info%s"%project,"当前任务空闲,正在编译".encode("gbk"),1)

                self.executeTest = threading.Thread(target=self.updateAndComplatThread, args=(product,project,int(timeout),))
                self.executeTest.start()
              
            else:
                UsedTime = time.perf_counter() - self.TestProcess[project]["startTime"]
                residueTime = self.TestProcess[project]["totalTime"] - UsedTime
                self.logger.info("%s %s %s"%(str(time.perf_counter()),str(self.TestProcess[project]["startTime"]),str(self.TestProcess[project]["totalTime"])))
                if residueTime > 0 :
                    self.on_publish("airm2m/server_info%s"%project,("上个版本没有测试完成,剩余时间:%d分钟"%(int(residueTime)/60)).encode("gbk"),1)
                else:
                    self.on_publish("airm2m/server_info%s"%project,"上个版本没有测试完成,剩余时间 不确定".encode("gbk"),1)
            
        elif msg.topic == "airm2m/cilent_endCnfat" or msg.topic == "airm2m/cilent_endCnflua" or msg.topic == "airm2m/cilent_endCnfcsdk" :  #测试结束返回
            projectMatch = re.match("airm2m/cilent_endCnf(.*)",msg.topic)
            if projectMatch:
                self.endTest(projectMatch.group(1))
                res,imei,state,data =  self.analysis_recv_log(payload,projectMatch.group(1))
                if res :
                    self.HandleEmail.SendTestEmail(self.TestProcess[project]["email"],"测试成功!!!\r\n%s\r\n"%(imei))
                else:
                    self.HandleEmail.SendTestEmail(self.TestProcess[project]["email"],"测试失败!!!\r\n%s\r\n%s"%(imei,data))



        elif msg.topic == "airm2m/cilent_circulateCnfat" or msg.topic == "airm2m/cilent_circulateCnflua" or msg.topic == "airm2m/cilent_circulateCnfcsdk" :  #循环测试返回
            projectMatch = re.match("airm2m/cilent_circulateCnf(.*)",msg.topic)
            if projectMatch:
                res,imei,state,data =  self.analysis_recv_log(payload,projectMatch.group(1))
                self.logger.info("res:%s imei:%s state:%s "%(str(res),str(imei),str(state)))
                self.TestProcess[projectMatch.group(1)]["circulateFlag"] = 0
                if state !=  "IDLE":
                    self.TestProcess[projectMatch.group(1)]["state"] = state
                    if res:
                        self.logger.info("cilent_circulateCnf  ok ")
                        self.on_publish("airm2m/server_info%s"%(projectMatch.group(1)),"轮询检查成功".encode("gbk"),1)
                    else:
                        self.logger.warning("cilent_circulateCnf  err %d %d"%(len(self.TestProcess[projectMatch.group(1)]["Errlog"]),len(data)))
                        if self.TestProcess[projectMatch.group(1)]["Errlog"] != data :
                            self.TestProcess[projectMatch.group(1)]["Errlog"] = data
                            self.HandleEmail.SendTestEmail(self.TestProcess[projectMatch.group(1)]["email"],data,att=True)
                            self.on_publish("airm2m/server_info%s"%(projectMatch.group(1)),"轮询检查失败，已发邮件".encode("gbk"),1)

                else:
                    self.logger.warning("cilent_circulateCnf  state err test end ")
                    self.HandleEmail.SendTestEmail(self.TestProcess[projectMatch.group(1)]["email"],"测试模块异常，需要确认是否死机!!!\r\n%s\r\n"%(imei))
                    self.on_publish("airm2m/server_info%s"%(projectMatch.group(1)),("测试模块异常，需要确认是否死机，需要确认是否死机%s"%(imei)).encode("gbk"),1)
                    self.on_publish("airm2m/server_stopreq%s"%(projectMatch.group(1)),None,1)

            
            
        elif msg.topic == "airm2m/cilent_UpgradeAndTestCnfat" or msg.topic == "airm2m/cilent_UpgradeAndTestCnflua" or msg.topic == "airm2m/cilent_UpgradeAndTestCnfcsdk":   #挂测开始回复
            projectMatch = re.match("airm2m/cilent_UpgradeAndTestCnf(.*)",msg.topic)
            if projectMatch:
                if payload.find("0") != -1:
                    self.logger.info("start test succes %s"%projectMatch.group(1))
                    self.on_publish("airm2m/server_info%s"%(projectMatch.group(1)),"测试PC 已经开始测试".encode("gbk"),1)
                    self.TestProcess[projectMatch.group(1)]["state"] = self.RUN
                else:
                    self.logger.error("start test fail %s"%projectMatch.group(1))
                    self.on_publish("airm2m/server_info%s"%(projectMatch.group(1)),"测试PC 开始测试失败,请查看原因".encode("gbk"),1)
                    self.TestProcess[projectMatch.group(1)]["state"] = self.IDLE
                
        elif msg.topic == "airm2m/cilent_StopCnfat" or msg.topic == "airm2mcilent_StopCnflua" or msg.topic == "airm2m/cilent_StopCnfcsdk":   #停止挂测回复
            projectMatch = re.match("airm2m/cilent_StopCnf(.*)",msg.topic)
            if projectMatch:
                if payload.find("0") != -1:
                    self.logger.info("start test succes %s"%projectMatch.group(1))
                    self.on_publish("airm2m/server_info%s"%(projectMatch.group(1)),"测试PC 停止测试成功".encode("gbk"),1)
                    self.endTest(projectMatch.group(1))
                else:
                    self.on_publish("airm2m/server_info%s"%(projectMatch.group(1)),"测试PC 停止测试失败".encode("gbk"),1)

            


    def on_subscribe(self):
        self.mqttClient.subscribe("airm2m/pc_publish", 1)

        self.mqttClient.subscribe("airm2m/cilent_endCnfat", 1)
        self.mqttClient.subscribe("airm2m/cilent_endCnflua", 1)
        self.mqttClient.subscribe("airm2m/cilent_endCnfcsdk", 1)
        
        self.mqttClient.subscribe("airm2m/cilent_circulateCnfat", 1)
        self.mqttClient.subscribe("airm2m/cilent_circulateCnflua", 1)
        self.mqttClient.subscribe("airm2m/cilent_circulateCnfcsdk", 1)

        self.mqttClient.subscribe("airm2m/cilent_UpgradeAndTestCnfat", 1)
        self.mqttClient.subscribe("airm2m/cilent_UpgradeAndTestCnflua", 1)
        self.mqttClient.subscribe("airm2m/cilent_UpgradeAndTestCnfcsdk", 1)

        self.mqttClient.subscribe("airm2m/cilent_StopCnfat", 1)
        self.mqttClient.subscribe("airm2m/cilent_StopCnflua", 1)
        self.mqttClient.subscribe("airm2m/cilent_StopCnfcsdk", 1)


        self.mqttClient.on_message = self.on_message_come  

    
    def exceute_timeout(self):
        self.logger.warning("complit timeout")
        self.executecommand.kill()
        pass


    def execute_command(self, cmd,sucess = False,timeout = 10*60 ,product = ""):
        self.logger.info('start executing cmd...')
     
        self.executecommand = subprocess.Popen(str(cmd), shell=True, stdin=subprocess.PIPE, stderr=subprocess.STDOUT,
                                               stdout=subprocess.PIPE)
        timer = threading.Timer(timeout,self.exceute_timeout)
        timer.start()
        line = self.executecommand.stdout.readline()

        while line:
            line = self.executecommand.stdout.readline()
            if line :
                try:
                    line =  str(line, encoding = "gbk")
                except Exception as e:
                    line = str(line)
                if sucess and line.find(sucess) != -1:
                    timer.cancel()
                    self.executecommand.kill()
                    self.executecommand = False
                    return line

                if product.find("8910") != -1:    
                    MMM = re.match("\[(\d+)/(\d+)\]",line)
                    if MMM :
                        self.logger.info("%s %s"%(MMM.group(1),MMM.group(2)))
                        if MMM.group(1) == MMM.group(2):
                            timer.cancel()
                            self.executecommand.kill()
                            self.executecommand = False
                            return True
                
            else:
                break
        timer.cancel()
        self.executecommand.kill()
        self.executecommand = False
        return False

    def startMqtt(self):
        MQTTHOST = "127.0.0.1"
        MQTTPORT = 1883
        self.mqttClient = mqtt.Client()
        self.mqttClient.connect(MQTTHOST, MQTTPORT, 60)
        self.mqttClient.loop_start()
        pass

    def update_svn(self,product,project):
        try:
            old_svn = self.TestProcess[project]["svnRevison"]
            if product == "8910v1.2" :
                os.chdir(self.AIRSOURCE+"8910v1.2")
                self.TestProcess[project]["svnRevison"] = self.execute_command('svn update',sucess ="revision")
                os.chdir(self.AIRSOURCE+"8910v1.2\components\\at\src")
                self.TestProcess[project]["svnRevison"] = self.TestProcess[project]["svnRevison"] + self.execute_command('svn update',sucess ="revision")
                os.chdir(self.MYPLACE)
                
            elif product == "8910v1.3" :
                os.chdir(self.AIRSOURCE+"8910v1.3")
                self.TestProcess[project]["svnRevison"] = self.execute_command('svn update',sucess = "revision")
                os.chdir(self.AIRSOURCE+"8910v1.3\components\\at\src")
                self.TestProcess[project]["svnRevison"] = self.TestProcess[project]["svnRevison"] + self.execute_command('svn update',sucess = "revision")
                os.chdir(self.MYPLACE)
            self.logger.info("update svn old:%s new:%s"%(old_svn,self.TestProcess[project]["svnRevison"]))
            return True,(old_svn != self.TestProcess[project]["svnRevison"])
        except Exception as  E:
            self.logger.info("update err %s"%E)
            return False,False

    def GetPacFile(self,project):
        self.logger.info("find pac")
        fileList = os.listdir("hex")
        for i in fileList:
            if i.find("zip") == -1 and i.find("MAP") == -1:
                fileList = os.listdir("hex"+"\\"+i)
                for j in fileList:
                    if j.find("pac") != -1 and j.lower().find(project.lower()) != -1  :
                        return "hex" + "\\" + i + "\\" + j
        return False

    def on_publish(self,topic,mes,qos):
        self.logger.info("on_publish %s"%(str(topic)))
        self.mqttClient.publish(topic, mes, qos)

    def GetFileList(self,dir, fileList):
        newDir = dir
        if os.path.isfile(dir):         # 如果是文件则添加进 fileList
            fileList.append(dir)
        elif os.path.isdir(dir):
            for s in os.listdir(dir):   # 如果是文件夹
                newDir = os.path.join(dir, s)
                if newDir.find(".svn") == -1 :
                    self.GetFileList(newDir, fileList)
        return fileList


    def run(self):
        self.start_server()
        self.startMqtt()
        self.on_subscribe()
        while(True):
            time.sleep(30)
        pass

    def combinScrpt(self,path):
        self.logger.info("combinScrpt")
        # os.chdir(self.AIRSOURCE+"lib")
        # os.system('svn update')
        os.chdir(self.AIRSOURCE+"publishTest")
        os.system('svn update')
        os.chdir(self.MYPLACE)
        all_scrp = []
        self.GetFileList(self.AIRSOURCE+"lib" ,all_scrp )
        self.GetFileList(self.AIRSOURCE+"publishTest" ,all_scrp )

        result, error = self.check_script.clean_file( all_scrp)
        result, error = self.check_script.check_file_depends()
        self.check_script.add_trace_file(2)
        result, bin_data = self.check_script.merge_script(self.scriptSize)
        if result :
            with open(path, 'wb') as f:
                f.write(bin_data)
        else:
            self.logger.info("merge err %s"%(bin_data))

        pass


if __name__ == '__main__':
    handle = mqttRun()
    handle.run()
  
